﻿/*--------------------------------------------
 * -------------------------------------------
 * --- CLASS MEDIAN FILTER -------------------
 * -------------------------------------------
 * Created By: Roy Scheefhals 
 *             / Kasper van Voorst
 * 
 * usage: Voert het median filter uit op een
 * Byte array ( 1 channel )
 * 
 * 
*/

namespace LibraryCS {
    public class MedianFilter {
        int fs = 0;
        public MedianFilter(int filterSize) {
            fs = filterSize;
        }

        // Deze MEDIAN methode slaat de grenspixels over!
        // hij laat deze zwart. geeft wel uiteindelijk een mooie zwarte lijst om het plaatje :).


        //function DoMedianFilter
        //Deze functie wordt gebruikt als geen threading wordt gebruikt
        //Hierin comment ik de werking van ons geniale 10waardige filter!
        public byte[] DoMedianFilter(byte[] channel, VImage mi) {


            byte[] output = new byte[mi.Width * mi.Height];
            byte median;
            int[] histogram = new int[256];

            // de radiaal van de filtergrootte ( dus filtergrootte /2 )
            int radFilter = fs / 2;

            //laat cols and rows beginnen op punt waar de hele kernel past
            int rows = fs / 2;//y
            int cols = fs / 2;//x
            int ker_row = rows; // DE Y
            int ker_col = cols; // DE X

            int directionvalue = 1; //lopen we naar rechts of naar links ?  +1 = rechts -1 = links
            int median_threshold;
            //de hoeveelste pixel is het midden van de kernel ( tellend vanaf linksboven naar rechtsonder )
            int middle = (fs * fs + 1) / 2;

            byte gray_value_old;
            byte gray_value_new;
            //--------------------------
            //begin algoritme
            //--------------------------

            //clear histogram
            for (int i = 0; i < 256; i++)
                histogram[i] = 0;

            // fill histogram voor eerste punt in plaatje.

            //     begin op linksboven van patch
            for (ker_row = 0; ker_row <= (rows + radFilter); ker_row++) {         // doe hierna de kolommen
                for (ker_col = 0; ker_col <= (cols + radFilter); ker_col++) {
                    histogram[channel[(ker_row * mi.Width) + ker_col]]++;
                }
                //ga naar de volgende rij
            }
            //Histogram voor de eerste pixel is gevuld
            //Get Median voor huidige X, Y ( rows,cols ) 
            int m = 0;
            // ga elke gray value af
            for (median = 0; median <= 255; median++) {
                //begin met de bekende gray values tellen ( tel omhoog )
                m += histogram[median];
                //als de teller over het midden ( of geijk aan het midden ) van de kernel gaat, dan hebben we onze mediaan
                if (m >= middle)
                    break;
            }
            //median_threshold geeft aan waar de oude mediaan zat in het histogram
            median_threshold = m - histogram[median];

            //update de pixel met de mediaan
            output[(rows * mi.Width) + cols] = median;

            //eerste pixel is geset met de goede median 
            //begin nu met het hele plaatje doorlopen
            //ga naar de volgende kolom
            cols++;

            // definitie welke de OUDE kolom/rij is en welke de NIEUWE in de histogram
            int oldCol, newCol, oldRow, newRow;
            // voor elke rij aan pixels
            for (; rows < mi.Height - radFilter; rows++) {   //doorloop elke kolom in de rij
                while (cols >= radFilter && cols < mi.Width - radFilter) // blijf binnen de array en binnen de grenspixels
                {
                    //bereken oude kolom en nieuwe kolom
                    oldCol = cols - directionvalue * (radFilter + 1);
                    newCol = cols + directionvalue * radFilter;
                    //update de histogram
                    // ga de rijen af om te updaten
                    for (ker_row = rows - radFilter; ker_row <= rows + radFilter; ker_row++) {
                        // pak de nieuwe en de oude gray value
                        gray_value_old = channel[(ker_row * mi.Width) + oldCol];
                        gray_value_new = channel[(ker_row * mi.Width) + newCol];

                        if (gray_value_old == gray_value_new)
                            continue;   // de waarden van nieuw en oud zijn hetzelfde, histogram veranderd niet
                        //verwijder oude grayvalue en voeg nieuwe toe;
                        histogram[gray_value_old]--;
                        histogram[gray_value_new]++;

                        //update threshold zodat hij vlakbij de mediaan blijft, maar niet eroverheen gaat
                        if (gray_value_old < median) // als de oude waarde onder de mediaan zat, dan wordt de NIEUWE mediaan een hogere waarde, dus de threshold moet een stap OMLAAG omdat daar de oude mediaan zat ( bij kernel met 9 pixels van 5 -> 4!)
                            median_threshold--;
                        if (gray_value_new < median)// als de nieuwe waarde onder de mediaan zit, dan wordt de NIEUWE mediaan een kleinere waarde. Dus de threshold moet een stap OMHOOG omdat daar de oude mediaan zat ( bij kernel met 9 pixels van 5 -> 6!)
                            median_threshold++;
                    }
                    // histogram is geupdate
                    //update nu de mediaan
                    if (median_threshold >= middle) // kijk of de oude mediaan boven het midden is gekomen ( dus de nieuwe mediaan zit ONDER de oude!!)
                    {
                        while (median_threshold >= middle) // ( bijf in de loop totdat het midden is gevonden )
                        {
                            if (histogram[--median] > 0) // bevat de histogram een value ( bestaat de waarde ? )
                            {
                                median_threshold -= histogram[median]; // update de threshold
                            }
                        }
                    }
                        // Waarom niet zoals hieronder?: ik kan niet stoppen als de threshold op het midden staat. 
                        // Want dat betekent dat de median die de threshold op het midden heeft gezet de pixel ( midden + 1 ) is !!!!!
                        // en de echte median kan dan heel veel pixels ver weg zitten. dus no can do
                    else // anders zit de huidige threshold onder het midden ( nieuwe mediaan zit BOVEN de oude!!)
                    {
                        while (median_threshold + histogram[median] < middle)// blijf in de loop totdat de threshold in het midden pixel komt
                        {
                            if (histogram[median] > 0)// bevat de histogram een value ( bestaat de waarde ? )
                            {
                                median_threshold += histogram[median]; // update de threshold
                            }
                            median++;
                        }//De threshold staat nu op het midden. de median is uitgerekend
                    }
                    output[(rows * mi.Width) + cols] = median;
                    cols += directionvalue;
                }
                if (rows < mi.Height - radFilter - 1) {
                    // beweeg de histogram 1 plek naar beneden en ga naar links
                    cols -= directionvalue;
                    // laat de direction omdraaien
                    directionvalue *= -1;


                    oldRow = (rows - radFilter) * mi.Width;
                    newRow = (rows + radFilter + 1) * mi.Width;

                    for (ker_col = cols - radFilter; ker_col <= cols + radFilter; ker_col++) {
                        gray_value_old = channel[oldRow + ker_col];
                        gray_value_new = channel[newRow + ker_col];
                        //verkrijg de nieuwe en de oude gray value

                        // ( hieronder gebeurt hetzelfde als bij een kolom wisseling )
                        if (gray_value_old == gray_value_new) {
                            continue;
                        }

                        histogram[gray_value_old]--;
                        histogram[gray_value_new]++;

                        if (gray_value_old < median) {
                            median_threshold--;
                        }
                        if (gray_value_new < median) {
                            median_threshold++;
                        }
                    }

                    /* 
                     * Hieronder update ik de mediaan voor de pixel in de nieuwe rij
                     * Zie comments in het vorige deel ( hier gebeurt hetzelfde )
                     */

                    if (median_threshold >= middle) {
                        while (median_threshold >= middle) {
                            if (histogram[--median] > 0) {
                                median_threshold -= histogram[median];
                            }
                        }
                    } else {
                        while (median_threshold + histogram[median] < middle) {
                            if (histogram[median] > 0) {
                                median_threshold += histogram[median];
                            }
                            median++;
                        }
                    }
                    output[((rows + 1) * mi.Width) + cols] = median; // update de nieuwe waarde
                    cols += directionvalue; // ga naar de volgende kolom ( er is al een pixel in die rij gedaan. maar dat is dan ook meteen de eerste kolom, dus skip! )
                }
            } // einde for loop alle rijen
            //DONE!!
            return output;
        }


        // DoMedianFilterThreaded function
        // deze wordt gebruikt by multithreading
        // hierin comment ik alleen dingen die veranderen wanneer er dus threading wordt gebruikt
        public void DoMedianFilterThreaded(byte[] channel, MedianExecute me, VImage mi, int beginRow, int endRow, bool processHalfImage, int id) {


            byte[] output = new byte[mi.Width * mi.Height];
            byte median;
            int[] histogram = new int[256];

            int radFilter = fs / 2;

            int rows = radFilter;//y
            int cols = radFilter;//x
            int ker_row = rows; // DE Y
            int ker_col = cols; // DE X

            int heightRange = mi.Height - radFilter - 1; ;

            if (processHalfImage) // als het multithreaded is?
            {
                if (beginRow < radFilter) rows = radFilter; // is de beginrij kleiner dan de helft van het filter ? dan moet de beginpixel op de eerste plek komen waar de kernel past!
                else rows = beginRow; // anders laat de rij wijzen naar de eerste rij die hij moet checken

                if (endRow > mi.Height - radFilter - 1) heightRange = mi.Height - radFilter - 1; // is de eindrij groter dan plaatje - de helft van het filter -1 ? laat hem dan de max hoogte houden zodat de kernel past op het einde
                else heightRange = endRow; // anders laat de eindrij wijzen naar de stopconditie die is meegegeven in de parameters
            }


            int directionvalue = 1;
            int median_threshold;
            int middle = (fs * fs + 1) / 2;

            byte gray_value_old;
            byte gray_value_new;
            //--------------------------
            //begin algoritme
            //--------------------------

            for (int i = 0; i < 256; i++)
                histogram[i] = 0;

            for (ker_row = rows - radFilter; ker_row <= (rows + radFilter); ker_row++) {
                for (ker_col = 0; ker_col <= (cols + radFilter); ker_col++) {
                    histogram[channel[(ker_row * mi.Width) + ker_col]]++;
                }
            }
            int m = 0;
            for (median = 0; median <= 255; median++) {
                m += histogram[median];
                if (m >= middle)
                    break;
            }
            median_threshold = m - histogram[median];

            output[(rows * mi.Width) + cols] = median;

            cols++;

            int oldCol, newCol, oldRow, newRow;

            for (; rows < heightRange; rows++) { // laat de stopconditie de heightrange zijn 
                while (cols >= radFilter && cols < mi.Width - radFilter) {
                    oldCol = cols - directionvalue * (radFilter + 1);
                    newCol = cols + directionvalue * radFilter;

                    for (ker_row = rows - radFilter; ker_row <= rows + radFilter; ker_row++) {
                        gray_value_old = channel[(ker_row * mi.Width) + oldCol];
                        gray_value_new = channel[(ker_row * mi.Width) + newCol];

                        if (gray_value_old == gray_value_new)
                            continue;

                        histogram[gray_value_old]--;
                        histogram[gray_value_new]++;


                        if (gray_value_old < median)
                            median_threshold--;
                        if (gray_value_new < median)
                            median_threshold++;
                    }
                    if (median_threshold >= middle) {
                        while (median_threshold >= middle) {
                            if (histogram[--median] > 0) {
                                median_threshold -= histogram[median];
                            }
                        }
                    } else {
                        while (median_threshold + histogram[median] < middle) {
                            if (histogram[median] > 0) {
                                median_threshold += histogram[median];
                            }
                            median++;
                        }
                    }
                    output[(rows * mi.Width) + cols] = median;
                    cols += directionvalue;
                }
                if (rows < heightRange) // check of ie wel in de range blijft
                {

                    cols -= directionvalue;

                    directionvalue *= -1;

                    oldRow = (rows - radFilter) * mi.Width;
                    newRow = (rows + radFilter + 1) * mi.Width;

                    for (ker_col = cols - radFilter; ker_col <= cols + radFilter; ker_col++) {
                        gray_value_old = channel[oldRow + ker_col];
                        gray_value_new = channel[newRow + ker_col];
                        if (gray_value_old == gray_value_new) {
                            continue;
                        }

                        histogram[gray_value_old]--;
                        histogram[gray_value_new]++;

                        if (gray_value_old < median) {
                            median_threshold--;
                        }
                        if (gray_value_new < median) {
                            median_threshold++;
                        }
                    }

                    if (median_threshold >= middle) {
                        while (median_threshold >= middle) {
                            if (histogram[--median] > 0)
                                median_threshold -= histogram[median];
                        }
                    } else {
                        while (median_threshold + histogram[median] < middle) {
                            if (histogram[median] > 0) {
                                median_threshold += histogram[median];
                            }
                            median++;
                        }
                    }
                    output[((rows + 1) * mi.Width) + cols] = median;
                    cols += directionvalue;
                }
            }

            //als deze thread klaar is, moet hij de MedianExecute klasse Notifyen dat deze thread klaar is. geef ook meteen het eindresultaat mee
            me.notifyMeWithChannel(output, id);
            return;
        }
    }
}
